package com.lagou.sqlSession;

import com.lagou.config.BoundSql;
import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;
import com.lagou.utils.GenericTokenParser;
import com.lagou.utils.ParameterMapping;
import com.lagou.utils.ParameterMappingTokenHandler;
import com.lagou.utils.TokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException {
        Connection connection = null;
        List<Object> objects = new ArrayList<>();
        try {
            PreparedStatement preparedStatement = this.getPreparedStatement(connection, configuration, mappedStatement, params);
            ResultSet resultSet = preparedStatement.executeQuery();
            String resultType = mappedStatement.getResultType();
            Class<?> resultTypeClass = this.getClassType(resultType);

            List<String> fieldsNames = new ArrayList<>();
            Field[] fields = resultTypeClass.getDeclaredFields();
            if (fields != null) {
                for (int i = 0; i < fields.length; i++) {
                    Field field = fields[i];
                    String name = field.getName();
                    if (fieldsNames.contains(name)) {
                        continue;
                    }
                    fieldsNames.add(name);
                }
            }

            while (resultSet.next()) {
                ResultSetMetaData metaData = resultSet.getMetaData();
                Object o = resultTypeClass.newInstance();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    String columnName = metaData.getColumnName(i);
                    if (!fieldsNames.contains(columnName)) {
                        continue;
                    }
                    Object value = resultSet.getObject(columnName);
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(o, value);
                }
                objects.add(o);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }

        return (List<E>) objects;
    }

    @Override
    public int doUpdate(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException {
        int result = 0;
        Connection connection = null;
        try{
            PreparedStatement preparedStatement = this.getPreparedStatement(connection, configuration, mappedStatement, params);
            result = preparedStatement.executeUpdate();
            if (connection != null) {
                connection.commit();
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (connection != null) {
                connection.rollback();
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if (paramterType != null) {
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
        return null;
    }

    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}", parameterMappingTokenHandler);
        String parseSql = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }

    private PreparedStatement getPreparedStatement(Connection connection, Configuration configuration, MappedStatement mappedStatement, Object... params)
            throws Exception {
        connection = configuration.getDataSource().getConnection();
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        String paramterType = mappedStatement.getParamterType();
        if (paramterType == null) {
            return preparedStatement;
        }
        Class<?> paramterClass = this.getClassType(paramterType);
        boolean isJavaLang = false;
        if (String.class.isAssignableFrom(paramterClass)
                || Number.class.isAssignableFrom(paramterClass)
                || Boolean.class.isAssignableFrom(paramterClass)
                || Character.class.isAssignableFrom(paramterClass)) {
            isJavaLang = true;
        }

        System.out.println("sql:" + boundSql.getSqlText());

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            Object o = null;
            if (isJavaLang) {
                Constructor constructor = paramterClass.getConstructor(String.class);
                o = params[0];
            } else {
                // 反射
                Field declaredField = paramterClass.getDeclaredField(content);
                // 暴力访问
                declaredField.setAccessible(true);
                o = declaredField.get(params[0]);
            }
            preparedStatement.setObject(i + 1, o);
            System.out.println("parameter{" + content + "}:" + o.toString());
        }
        return preparedStatement;
    }

}
